using System;

using MosCraft.Interfaces;

using MosCraft.Utilities;

namespace MosCraft.Terrain.ChunkProviders
{
    public class ChunkProviderPerlin : IChunkGenerator
    {
        private World world;
        private Random rand;

        public ChunkProviderPerlin(World world, long seed)
        {
            this.world = world;
            rand = new Random();
        }

        /*
         * Short cut method for adding blocks into the main array.
         */
        private void putBlock(ref byte[] blocks, int x, int y, int z, int blockID)
        {
            try
            {
                blocks[x << 11 | y << 7 | z] = (byte)blockID;
            }
            catch (Exception)
            {
                //This is by no means a good way to prevent errors, but it works.
            }
        }

        private void generateTerrain(ref byte[] blocks, int chunkX, int chunkZ)
        {
            int i = blocks.Length / 256;

            Perlin perlin = new Perlin(19, 16); //Set the seed to 19 and the frequency to 16 (temporary value, change to whatever you wish. All values inside the integer range are safe to use).
            /*
             * Generate the world shape out of stone.
             */
            for (int x = 0; x < 16; x++) //Loop to max width/depth
            {
                for (int y = 0; y < 16; y++) //Loop to max width/depth (other dimension)
                {
                    for (int z = 0; z < i; z++) //Loop to max height
                    {
                        int block = 0; // Air

                        //This is the old generation code:
                        //if (z == 0)
                        //{
                        //    block = Block.bedrock.blockID;
                        //}
                        //else if (z <= 7)
                        //{
                        //    block = Block.stone.blockID;
                        //}
                        //else if (z <= 20)
                        //{
                        //    block = Block.dirt.blockID;
                        //}
                        //else if (z == 21)
                        //{
                        //    block = Block.grass.blockID;
                        //}

                        //This is the new generation code:
                        if (z <= 20 * perlin.getNoiseLevelAtPosition(chunkX * 16 + x, chunkZ * 16 + y ))
                        {
                            block = Block.Stone.blockID;
                        }
                        blocks[x << 11 | y << 7 | z] = (byte)block;
                    }
                }
            }
            /*
             * Add dirt, grass and bedrock.
             */
            for (int x = 0; x < 16; x++)
            {
                for (int y = 0; y < 16; y++)
                {
                    for (int z = 0; z < i; z++)
                    {
                        //If the block above the tested block is air, and the tested block is stone:
                        if (blocks[x << 11 | y << 7 | z] == 1 && z < i && blocks[x << 11 | y << 7 | (z + 1)] == 0)
                        {
                            //Turn the tested block into grass, and the blocks below it into dirt.
                            putBlock(ref blocks, x, y, z, Block.Grass.blockID);
                            if (z > 0)
                            {
                                putBlock(ref blocks, x, y, z - 1, Block.Dirt.blockID);
                            }
                            if (z > 1)
                            {
                                putBlock(ref blocks, x, y, z - 2, Block.Dirt.blockID);
                            }
                            if (z > 2)
                            {
                                putBlock(ref blocks, x, y, z - 3, Block.Dirt.blockID);
                            }
                        }
                        //If at the lowest layer, turn everything into bedrock.
                        if (z == 0)
                        {
                            putBlock(ref blocks, x, y, z, Block.Bedrock.blockID);
                        }
                    }
                }
            }
            /*
             * -IN DEVELOPMENT-
             * Add some ore.
             */
             
             //Generate 4 veins of ore.
            /*for (int dCounter = 0; dCounter < 4; dCounter++)
            {
                //Randomly determine the vein size with a minimum of 2 and a maximum of 8 ore blocks.
                int oreCount = 2 + (int) Math.Round(6 * rand.NextDouble());
                switch (oreCount)
                {
                    //Extremely bulky custom ore vein generation code will go here.
                    case 2:
                        putBlock(ref blocks, i, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j+1, k, Block.Iron.BlockID);
                        break;
                    case 3:
                        putBlock(ref blocks, i, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j+1, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k, Block.Iron.BlockID);
                        break;
                    case 4:
                        putBlock(ref blocks, i, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j+1, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j, k+1, Block.Iron.BlockID);
                        break;
                    case 5:
                        putBlock(ref blocks, i, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j+1, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j, k+1, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k+1, Block.Iron.BlockID);
                        break;
                    case 6:
                        putBlock(ref blocks, i, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j+1, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k, Block.Iron.BlockID);
                        putBlock(ref blocks, i, j, k+1, Block.Iron.BlockID);
                        putBlock(ref blocks, i+1, j, k+1, Block.Iron.BlockID);
                        break;
                    case 7:
                    
                        break;
                    case 8:
                        break;
                    default:
                        break;
                }
            }*/
        }

        public void Generate(ref Chunk chunk)
        {
            byte[] blocks = new byte[32768];
            int x = chunk.xPosition;
            int z = chunk.zPosition;
            this.generateTerrain(ref blocks, x, z);

            chunk.setData(blocks);
        }

        public bool chunkExists(int x, int z)
        {
            return true;
        }

        public void Populate(int i, int j)
        {

        }
    }
}
